/*
*
* Copyright (c) 2011 Vaulting Systems International
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy 
* of this software and associated documentation files (the "Software"), to deal 
* in the Software without restriction, including without limitation the rights 
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
* of the Software, and to permit persons to whom the Software is furnished to do  
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all  
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE  
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.ekeymanlib.test;

import java.security.Security;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

import com.ekeyman.utils.DigitalSignature;
import com.ekeymanlib.domain.User;
import com.ekeymanlib.dto.AppDeviceApiKeys;
import com.ekeymanlib.dto.EncryptionKeys;
import com.ekeymanlib.dto.VendorProfile;
import com.ekeymanlib.ws.EkeymanAdminWS;
import com.ekeymanlib.ws.EkeymanClientWS;

public class EkeyManWSaesUT {
	
	private static final String[] SPRING_CONFIG_FILES = new String[]{"applicationContext-lib.xml"};
	private EkeymanAdminWS ekeymanAdminWS;
	private EkeymanClientWS ekeymanClientWS;
	ClassPathXmlApplicationContext ctx;
	
	
	@Parameters({"openidurl","apiKey","emailAddress","application","device","resourceUri"}) 
	@Test
	public void testEnd2End(
			@Optional("http://my.yahoo.com/openid/jimmy100/") String openidurl,
			@Optional("") String apiKey,
			@Optional("james.bell01@estateplusplus.com") String emailAddress,
			@Optional("HighlySecureAndroidFinancialApp") String application,
			@Optional("704-942-7257") String device,
			@Optional("BillPayTable/AccountNumberColumn") String resourceUri) {
		
		System.out.println("Entered testEnd2End...");
		
		User user = getEkeymanClientWS().getUser(openidurl);
		
		if(user == null){
			if(apiKey == null || apiKey.equalsIgnoreCase("")){
				apiKey = getEkeymanAdminWS().registerVendor(
						"Jimmy Bell", 
						"7339 Castleton Road", 
						"Charlotte", 
						"NC", 
						"28211", 
						"704-942-7257", 
						emailAddress);
			}
			getEkeymanAdminWS().registerUser(apiKey, openidurl);
		}
		else{
			apiKey = user.getVendor().getApiKey();
		}

		VendorProfile vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile != null : "Expected vendorProfile to be not null";
		
		AppDeviceApiKeys appDeviceApiKeys = getEkeymanClientWS().registerAppDevice(
				vendorProfile.getApiKey(), application, device);
		
		String location;
		String secretKeyType = "AES"; //javax.crypto.KeyGenerator algorithm name
		int secretKeyLength = 16; // KeyGenerator algorithm-specific metric specified in number of bytes. 
		int ivLength = 8; // in bytes
		int saltLength = 8; // in bytes
		int iterationCountMax = 1024;
	    try {
	    	location = "Current cellphone GPS coordinates for createEncryptionKeys";
			String requestDigitalSignature = DigitalSignature.createDigitalSignature(
					appDeviceApiKeys.getPublicKey(), appDeviceApiKeys.getPrivateKey(), 
					Integer.toString(secretKeyLength), Integer.toString(ivLength), 
					Integer.toString(saltLength), Integer.toString(iterationCountMax),
					location);
					
	    	EncryptionKeys keysToEncrypt = getEkeymanClientWS().createEncryptionKeys(resourceUri, 
	    			appDeviceApiKeys.getPublicKey(), secretKeyLength, ivLength, 
					saltLength, iterationCountMax, location, requestDigitalSignature);
		    
	    	SecretKeySpec encryptionKey = new SecretKeySpec(keysToEncrypt.getKeyBytes(), secretKeyType);
		    
		    // String to encrypt
			// Beware of the infamous javax.crypto.IllegalBlockSizeException: data not block size aligned
			// Extra blank need to pad to 16 characters (needs to be multiple of 8)
			String originalText = "983457863123459 "; 
		    byte[] input = originalText.getBytes();
		    Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC"); // Uses bouncy castle provider
		    System.out.println("input text : " + new String(input));
	
		    /*
		     * Special note... cipher.init() could fail with: 
		     * java.security.InvalidKeyException: Illegal key size 
		     * 
		     * You will need to install the Java Cryptography Extension (JCE) 
		     * Unlimited Strength Jurisdiction Policy Files (available at Oracle).
			 * If you don't, the keysize is limited due to US export laws.
			 * 
		     */
		    
		    // encryption pass
		    byte[] cipherText = new byte[input.length];
		    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
		    int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
		    ctLength += cipher.doFinal(cipherText, ctLength);
		    String ct = new String(cipherText);
		    System.out.println("cipher text: " + ct + " bytes: " + ctLength);
		    
			String computedDigitalSignature = DigitalSignature.createDigitalSignature(resourceUri, 
					appDeviceApiKeys.getPublicKey(), appDeviceApiKeys.getPrivateKey(), location);
		    
			EncryptionKeys keysToDecrypt = getEkeymanClientWS().getEncryptionKeys(resourceUri, 
					appDeviceApiKeys.getPublicKey(), location, computedDigitalSignature);
			
		    // decryption pass
		    SecretKeySpec decryptionKey = new SecretKeySpec(keysToDecrypt.getKeyBytes(), secretKeyType);
		    byte[] plainText = new byte[ctLength];
		    cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
		    int ptLength = cipher.update(cipherText, 0, ctLength, plainText, 0);
	    
		    ptLength += cipher.doFinal(plainText, ptLength);
		    String decryptedText = new String(plainText);
		    System.out.println("plain text : " + decryptedText + " bytes: " + ptLength);
		    
		    // At this point the original text and decrypted text should match!!!!
		    assert originalText.equals(decryptedText) : "Expected originalText and decrypted text to match" + 
		    		" originalText: " + originalText + " decryptedText: " + decryptedText;
		    
		    getEkeymanAdminWS().deleteVendor(apiKey);
	    
		    
	    }
	    catch (Exception e){
	    	System.out.println(e.getMessage());
	    }
		System.out.println("Exited testEnd2End...");
	}
	
	@Parameters({"openidurl","apiKey","emailAddress","application","device","resourceUri"}) 
	@Test
	public void testGenerateNewPrivateKey(
			@Optional("http://my.yahoo.com/openid/jimmy200/") String openidurl,
			@Optional("") String apiKey,
			@Optional("james.bell02@estateplusplus.com") String emailAddress,
			@Optional("HighlyInsecureAndroidFinancialApp") String application,
			@Optional("312-474-8645") String device,
			@Optional("BillPayTable/AccountNumberColumn") String resourceUri) {
		
		System.out.println("Entered testGenerateNewPrivateKey...");
		
		User user = getEkeymanClientWS().getUser(openidurl);
		
		if(user == null){
			if(apiKey == null || apiKey.equalsIgnoreCase("")){
				apiKey = getEkeymanAdminWS().registerVendor(
						"Jimmy Bell", 
						"5339 Castleton Road", 
						"Charlotte", 
						"NC", 
						"28211", 
						"704-942-7257", 
						emailAddress);
			}
			getEkeymanAdminWS().registerUser(apiKey, openidurl);
		}
		else{
			apiKey = user.getVendor().getApiKey();
		}

		VendorProfile vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile != null : "Expected vendorProfile to be not null";

		AppDeviceApiKeys appDeviceApiKeys = getEkeymanClientWS().registerAppDevice(
				vendorProfile.getApiKey(), application, device);
		
		System.out.println("public key: " + appDeviceApiKeys.getPublicKey() +
				" private key: " + appDeviceApiKeys.getPrivateKey());
		
		String location;
		String secretKeyType = "AES"; //javax.crypto.KeyGenerator algorithm name
		int secretKeyLength = 16; // KeyGenerator algorithm-specific metric specified in number of bytes. 
		int ivLength = 8; // in bytes
		int saltLength = 8; // in bytes
		int iterationCountMax = 1024;

	    try {
	    	location = "Current cellphone GPS coordinates for createEncryptionKeys";
			String requestDigitalSignature = DigitalSignature.createDigitalSignature(
					appDeviceApiKeys.getPublicKey(), appDeviceApiKeys.getPrivateKey(), 
					Integer.toString(secretKeyLength), Integer.toString(ivLength), 
					Integer.toString(saltLength), Integer.toString(iterationCountMax),
					location);
					
	    	EncryptionKeys keysToEncrypt = getEkeymanClientWS().createEncryptionKeys(resourceUri, 
	    			appDeviceApiKeys.getPublicKey(), secretKeyLength, ivLength, 
					saltLength, iterationCountMax, 
					location, requestDigitalSignature);
		    
	    	SecretKeySpec encryptionKey = new SecretKeySpec(keysToEncrypt.getKeyBytes(), secretKeyType);
		    
		    // String to encrypt
			// Beware of the infamous javax.crypto.IllegalBlockSizeException: data not block size aligned
			// Extra blank need to pad to 16 characters (needs to be multiple of 8)
			String originalText = "983457863123459 "; 
		    byte[] input = originalText.getBytes();
		    Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC"); // Uses bouncy castle provider
		    System.out.println("input text : " + new String(input));
	
		    /*
		     * Special note... cipher.init() could fail with: 
		     * java.security.InvalidKeyException: Illegal key size 
		     * 
		     * You will need to install the Java Cryptography Extension (JCE) 
		     * Unlimited Strength Jurisdiction Policy Files (available at Oracle).
			 * If you don't, the keysize is limited due to US export laws.
			 * 
		     */
		    
		    // encryption pass
		    byte[] cipherText = new byte[input.length];
		    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
		    int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
		    ctLength += cipher.doFinal(cipherText, ctLength);
		    String ct = new String(cipherText);
		    System.out.println("cipher text: " + ct + " bytes: " + ctLength);
		    
		    
		    // Change private key and try to decrypt 
		    AppDeviceApiKeys changedAppDeviceApiKeys = getEkeymanAdminWS().generateNewPrivateKey(appDeviceApiKeys.getPublicKey(), apiKey);
		    
		    System.out.println("generateNewPrivateKey...");
			System.out.println("public key: " + changedAppDeviceApiKeys.getPublicKey() +
					" private key: " + changedAppDeviceApiKeys.getPrivateKey());

			// Need to test that private key changed
		    assert !(appDeviceApiKeys.getPrivateKey().equals(changedAppDeviceApiKeys.getPrivateKey())) : 
		    		"Expected appDeviceApiKeys.getPrivateKey() and changedAppDeviceApiKeys.getPrivateKey() to be different" + 
		    		" appDeviceApiKeys.getPrivateKey(): " + appDeviceApiKeys.getPrivateKey() + 
		    		" changedAppDeviceApiKeys.getPrivateKey(): " + changedAppDeviceApiKeys.getPrivateKey();

			
			// Use old private key as client doesn't know it has changed
			String computedDigitalSignature = DigitalSignature.createDigitalSignature(resourceUri, 
					appDeviceApiKeys.getPublicKey(), appDeviceApiKeys.getPrivateKey(), location);
		    
			EncryptionKeys keysToDecrypt = getEkeymanClientWS().getEncryptionKeys(resourceUri, 
					appDeviceApiKeys.getPublicKey(), location, computedDigitalSignature);
			
		    // decryption pass
		    SecretKeySpec decryptionKey = new SecretKeySpec(keysToDecrypt.getKeyBytes(), secretKeyType);
		    byte[] plainText = new byte[ctLength];
		    cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
		    int ptLength = cipher.update(cipherText, 0, ctLength, plainText, 0);
	    
		    ptLength += cipher.doFinal(plainText, ptLength);
		    String decryptedText = new String(plainText);
		    System.out.println("plain text : " + decryptedText + " bytes: " + ptLength);
		    
		    // At this point the original text and decrypted text should match!!!!
		    assert !(originalText.equals(decryptedText)) : "Expected originalText and decrypted text to be different" + 
		    		" originalText: " + originalText + " decryptedText: " + decryptedText;
		    
		    getEkeymanAdminWS().deleteVendor(apiKey);
	    
		    
	    }
	    catch (Exception e){
	    	System.out.println(e.getMessage());
	    }
		System.out.println("Exited testGenerateNewPrivateKey...");
	}
	
	@Parameters({"openidurl","apiKey","emailAddress","application","device","resourceUri"}) 
	@Test
	public void testAlreadyRegistered(
			@Optional("http://my.yahoo.com/openid/jimmy300/") String openidurl,
			@Optional("") String apiKey,
			@Optional("james.bell03@estateplusplus.com") String emailAddress,
			@Optional("AnothersecureAndroidFinancialApp") String application,
			@Optional("219-845-8960") String device,
			@Optional("BillPayTable/AccountNumberColumn") String resourceUri) {
		
		System.out.println("Entered testAlreadyRegistered...");
		
		apiKey = getEkeymanAdminWS().registerVendor(
				"Jimmy Bell", 
				"8339 Castleton Road", 
				"Charlotte", 
				"NC", 
				"28211", 
				"704-942-7257", 
				emailAddress);
		
		getEkeymanAdminWS().registerUser(apiKey, openidurl);
				
		User user = getEkeymanClientWS().getUser(openidurl);
		
	    assert user != null : "Expected user to be not null";
	    
		apiKey = user.getVendor().getApiKey();

		VendorProfile vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile != null : "Expected vendorProfile to be not null";
		
		AppDeviceApiKeys appDeviceApiKeys = getEkeymanClientWS().registerAppDevice(
				vendorProfile.getApiKey(), device, application);
		
		String location;
		String secretKeyType = "AES"; //javax.crypto.KeyGenerator algorithm name
		int secretKeyLength = 16; // KeyGenerator algorithm-specific metric specified in number of bytes. 
		int ivLength = 8; // in bytes
		int saltLength = 8; // in bytes
		int iterationCountMax = 1024;
	    try {
	    	location = "Current cellphone GPS coordinates for createEncryptionKeys";
			String requestDigitalSignature = DigitalSignature.createDigitalSignature(
					appDeviceApiKeys.getPublicKey(), appDeviceApiKeys.getPrivateKey(), 
					Integer.toString(secretKeyLength), Integer.toString(ivLength), 
					Integer.toString(saltLength), Integer.toString(iterationCountMax),
					location);
					
	    	EncryptionKeys keysToEncrypt = getEkeymanClientWS().createEncryptionKeys(resourceUri, 
	    			appDeviceApiKeys.getPublicKey(), secretKeyLength, ivLength, 
					saltLength, iterationCountMax, location, requestDigitalSignature);
		    
	    	SecretKeySpec encryptionKey = new SecretKeySpec(keysToEncrypt.getKeyBytes(), secretKeyType);
		    
		    // String to encrypt
			// Beware of the infamous javax.crypto.IllegalBlockSizeException: data not block size aligned
			// Extra blank need to pad to 16 characters (needs to be multiple of 8)
			String originalText = "983457863123459 "; 
		    byte[] input = originalText.getBytes();
		    Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC"); // Uses bouncy castle provider
		    System.out.println("input text : " + new String(input));
	
		    /*
		     * Special note... cipher.init() could fail with: 
		     * java.security.InvalidKeyException: Illegal key size 
		     * 
		     * You will need to install the Java Cryptography Extension (JCE) 
		     * Unlimited Strength Jurisdiction Policy Files (available at Oracle).
			 * If you don't, the keysize is limited due to US export laws.
			 * 
		     */
		    
		    // encryption pass
		    byte[] cipherText = new byte[input.length];
		    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
		    int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
		    ctLength += cipher.doFinal(cipherText, ctLength);
		    String ct = new String(cipherText);
		    System.out.println("cipher text: " + ct + " bytes: " + ctLength);
		    
			String computedDigitalSignature = DigitalSignature.createDigitalSignature(resourceUri, 
					appDeviceApiKeys.getPublicKey(), appDeviceApiKeys.getPrivateKey(), location);
		    
			EncryptionKeys keysToDecrypt = getEkeymanClientWS().getEncryptionKeys(resourceUri, 
					appDeviceApiKeys.getPublicKey(), location, computedDigitalSignature);
			
		    // decryption pass
		    SecretKeySpec decryptionKey = new SecretKeySpec(keysToDecrypt.getKeyBytes(), secretKeyType);
		    byte[] plainText = new byte[ctLength];
		    cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
		    int ptLength = cipher.update(cipherText, 0, ctLength, plainText, 0);
	    
		    ptLength += cipher.doFinal(plainText, ptLength);
		    String decryptedText = new String(plainText);
		    System.out.println("plain text : " + decryptedText + " bytes: " + ptLength);
		    
		    // At this point the original text and decrypted text should match!!!!
		    assert originalText.equals(decryptedText) : "Expected originalText and decrypted text to match" + 
		    		" originalText: " + originalText + " decryptedText: " + decryptedText;
		    
		    getEkeymanAdminWS().deleteVendor(apiKey);
	    
		    
	    }
	    catch (Exception e){
	    	System.out.println(e.getMessage());
	    }
		System.out.println("Exited testAlreadyRegistered...");
	}

	
	@Parameters({"openidurl","emailAddress"}) 
	@Test
	public void testDisabledVendor(
			@Optional("http://my.yahoo.com/openid/jimmy400/") String openidurl,
			@Optional("james.bell03@estateplusplus.com") String emailAddress) {
		
		System.out.println("Entered testDisabledVendor...");
		
		User user = null;
		VendorProfile vendorProfile = null;
		
		String apiKey = getEkeymanAdminWS().registerVendor(
				"Jimmy Bell", 
				"8339 Castleton Road", 
				"Charlotte", 
				"NC", 
				"28211", 
				"704-942-7257", 
				emailAddress);
		
		getEkeymanAdminWS().registerUser(apiKey, openidurl);

		user = getEkeymanClientWS().getUser(openidurl);
	    assert user != null : "Expected user to be not null";
	    
		vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile != null : "Expected vendorProfile to be not null";
	    
		getEkeymanAdminWS().deleteVendor(apiKey);

		vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile == null : "Expected vendorProfile to be null";

		user = getEkeymanClientWS().getUser(openidurl);
	    assert user == null : "Expected user to be null";

		System.out.println("Exited testDisabledVendor...");
	}
	
	@Parameters({"openidurl","emailAddress"}) 
	@Test
	public void testDisabledUser(
			@Optional("http://my.yahoo.com/openid/jimmy500/") String openidurl,
			@Optional("james.bell03@estateplusplus.com") String emailAddress) {
		
		System.out.println("Entered testDisabledUser...");
		
		User user = null;
		VendorProfile vendorProfile = null;
		
		String apiKey = getEkeymanAdminWS().registerVendor(
				"Jimmy Bell", 
				"8339 Castleton Road", 
				"Charlotte", 
				"NC", 
				"28211", 
				"704-942-7257", 
				emailAddress);
		
		getEkeymanAdminWS().registerUser(apiKey, openidurl);
				
		user = getEkeymanClientWS().getUser(openidurl);
	    assert user != null : "Expected user to be not null";
	    
		vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile != null : "Expected vendorProfile to be not null";
	    
		getEkeymanAdminWS().deleteUser(openidurl);

		vendorProfile = getEkeymanAdminWS().getVendorProfile(apiKey);
	    assert vendorProfile != null : "Expected vendorProfile to be not null";

		user = getEkeymanClientWS().getUser(openidurl);
	    assert user == null : "Expected user to be null";

		System.out.println("Exited testDisabledUser...");
	}
	
	@BeforeSuite
	public void beforeSuite() {
		System.out.println("Before suite...");
		
		ctx = new ClassPathXmlApplicationContext(SPRING_CONFIG_FILES);
		
		setEkeymanClientWS((EkeymanClientWS)ctx.getBeanFactory().getBean("wsEkeymanClient"));
		setEkeymanAdminWS((EkeymanAdminWS)ctx.getBeanFactory().getBean("wsEkeymanAdmin"));
		
		// Bouncy Castle 1.4.6 jars installed in JAVA_HOME/jre/lib/ext
		// Service provider added to JAVA_HOME/jre/lib/security/java.security
		// security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 
	}
	
	@AfterSuite
	public void afterSuite() {
		System.out.println("After suite...");
	}
	
	public void setEkeymanAdminWS(EkeymanAdminWS ekeymanAdminWS) {
		this.ekeymanAdminWS = ekeymanAdminWS;
	}
	public EkeymanAdminWS getEkeymanAdminWS() {
		return ekeymanAdminWS;
	}

	public EkeymanClientWS getEkeymanClientWS() {
		return ekeymanClientWS;
	}
	public void setEkeymanClientWS(EkeymanClientWS ekeymanClientWS) {
		this.ekeymanClientWS = ekeymanClientWS;
	}

}
